/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
 */
package td.server.handler;

import java.util.Date;

import td.domain.Thinker;
import td.domain.util.LoadGroup;
import td.server.dao.ThinkerDao;
import td.shared.Constants;
import td.shared.Constants.MSG;
import td.shared.action.AbstractAction;
import td.shared.action.result.AbstractResult;
import td.shared.action.result.AbstractResult.Status;

import com.google.inject.Inject;
import com.gwtplatform.dispatch.server.ExecutionContext;
import com.gwtplatform.dispatch.server.actionhandler.ActionHandler;
import com.gwtplatform.dispatch.shared.ActionException;

/**
 * Base class for all handlers that performs validation.
 * 
 * @author Artem Shamsutdinov
 * 
 */
public abstract class AbstractHandler<A extends AbstractAction<R>, R extends AbstractResult>
		implements ActionHandler<A, R> {

	@Inject
	private ThinkerDao thinkerDao;

	boolean isProtected = true;
	private Thinker thinker = null;
	private Class<? extends LoadGroup>[] thinkerLoadGroups = null;

	protected AbstractHandler(boolean isProtected) {
		this.isProtected = isProtected;
	}

	protected final void setThinkerLoadGroups(
			Class<? extends LoadGroup>... thinkerLoadGroups) {
		this.thinkerLoadGroups = thinkerLoadGroups;
	}

	protected final Class<? extends LoadGroup>[] getThinkerLoadGroups() {
		return thinkerLoadGroups;
	}

	@Override
	public final R execute(A action, ExecutionContext context)
			throws ActionException {
		doSetup(action);
		try {
			validate(action);
		} catch (ValidationException ve) {
			return getErroneousResult(ve.getStatus(), ve.getMessage());
		}
		return doExecute(action, context);
	}

	/**
	 * Perform verification. Extending classes must make super.validate(A) call at
	 * the top of their implementations.
	 * 
	 * @param action
	 * @return Erroneous result set if verification failed, null otherwise
	 * @throws ValidationException
	 */
	protected void validate(A action) throws ValidationException {
		Long thinkerId = action.getThinkerId();
		String sessionCode = action.getSessionCode();

		if (isProtected) {
			if (sessionCode == null || thinkerId == null) {
				throw new ValidationException(Status.ACCESS_ERROR,
						MSG.ACTION_PROTECTED_MUST_HAVE_CREDENTIALS);
			}
			if (!verifyThinker(thinkerId, sessionCode)) {
				throw new ValidationException(Status.ACCESS_ERROR,
						MSG.UNABLE_TO_EXECUTE_PROTECTED_ACTION);
			}
		} else if (sessionCode != null && thinkerId != null) {
			if (!verifyThinker(thinkerId, sessionCode)) {
				throw new ValidationException(Status.ACCESS_ERROR,
						MSG.UNABLE_TO_EXECUTE_UNPROTECTED_ACTION);
			}
		}
		if (thinker != null) {
			Date lastAccess = thinker.getLastAccessDate();
			if (lastAccess.before(new Date(System.currentTimeMillis()
					- Constants.SESSION_TIMEOUT))) {
				throw new ValidationException(Status.TIMED_OUT, MSG.SESSION_TIMED_OUT);
			}
			thinker.setLastAccessDate(new Date());
			thinkerDao.put(thinker);
		}
	}

	/**
	 * Perform setup here (like specifying additional load groups)
	 */
	protected void doSetup(A action) {
		// Default implementation, override if needed
	}

	private boolean verifyThinker(Long thinkerId, String sessionCode) {
		Thinker thinker = thinkerDao.get(thinkerId, thinkerLoadGroups);
		if (thinker == null || !sessionCode.equals(thinker.getSessionCode())) {
			return false;
		}
		setThinker(thinker);
		return true;
	}

	@Override
	public void undo(A action, R result, ExecutionContext context)
			throws ActionException {
		// Default implementation, override if needed
	}

	/**
	 * Perform the actual execution here.
	 * 
	 * @param action
	 * @param context
	 * @return Action's return value
	 */
	protected abstract R doExecute(A action, ExecutionContext context);

	protected abstract R getErroneousResult(Status status, String errorMessage);

	public Thinker getThinker() {
		return thinker;
	}

	protected void setThinker(Thinker thinker) {
		this.thinker = thinker;
	}

}
